﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Security;
using System.Security.Policy;
using System.Text;
using System.Threading;
using System.Web;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Xsl;
using RemoteTestRunner.Contracts;
using System.Linq.Expressions;

namespace RemoteTestRunner.BusinessLogic
{
    public class RtrLib : MarshalByRefObject, IRTRService
    {
        #region Private Attributes

        private const string MultiAssertFileSuffix = ".MultiAssertOutput.xml";


        #endregion

        #region IRTRService Properties

        private DataAccess.DataAccess _dataAccess;
        public DataAccess.DataAccess DataAccess
        {
            get
            {
                if (_dataAccess == null)
                {
                    _dataAccess = new DataAccess.DataAccess(RtrSettings.DataPath);
                }

                return _dataAccess;
            }

            private set { _dataAccess = value; }
        }

        private DataAccess.ProjectDataAccess _projectsDataAccess;
        public DataAccess.ProjectDataAccess ProjectsDataAccess
        {
            get
            {
                if (_projectsDataAccess == null)
                {
                    _projectsDataAccess = new DataAccess.ProjectDataAccess(RtrSettings.DataPath);
                }

                return _projectsDataAccess;
            }

            private set { _projectsDataAccess = value; }
        }

        #endregion

        #region Static Methods

        /// <summary>
        /// Starts an e-mail message using the default e-mail client.
        /// </summary>
        /// <param name="subject">The subject.</param>
        /// <param name="messageBody">The message body.</param>
        public static void StartEmail(string subject, string messageBody)
        {
            Process.Start(
                string.Format("mailto:?subject={0}&body={1}", HttpUtility.UrlPathEncode(subject), messageBody));
        }

        /// <summary>
        /// Starts an e-mail message using the default e-mail client.
        /// </summary>
        /// <param name="testResult">An instance of a <see cref="TestResult"/> objected
        /// containing the details for the test run.</param>
        public static void StartEmail(TestResult testResult)
        {
            var subject = "Service Level Test Notification [{0}]";
            var message =
                "Test Run: {6}%0ATotal: {0}  Executed: {1}  Passed: {2}  Failed: {3}  Timed Out: {4}  Inconclusive: {5} %0A%0A";
            int dataRow;

            message = string.Format(
                message,
                testResult.TotalCount,
                testResult.TotalExecuted,
                testResult.TotalPassed,
                testResult.TotalFailed,
                testResult.TotalTimedOut,
                testResult.TotalInconclusive,
                testResult.TestRunName);

            foreach (var item in testResult.TestResults)
            {
                // Do not e-mail details on passed tests
                if (!item.Passed)
                {
                    message += item + " %0A%0A";
                }

                if (item.DataDrivenResults != null)
                {
                    dataRow = 0;

                    foreach (var ddItem in item.DataDrivenResults)
                    {
                        dataRow++;

                        if (!ddItem.Passed)
                        {
                            message += string.Format("     ==> [row: {0}]", dataRow) +
                                       ddItem + " %0A%0A";
                        }
                    }
                }
            }

            if (testResult.Outcome == "Completed")
            {
                subject = string.Format(subject, "Passed");
            }
            else if (testResult.TotalCount > 0)
            {
                subject = string.Format(subject, "Failed");
            }
            else
            {
                subject = string.Format(subject, "No Tests Run");
            }

            StartEmail(subject, message);
        }

        #endregion

        #region Public Methods

        #region Delegates

        public delegate TestResultSummary RunTestDelegate(Project project, Test test, List<string> testsToRun, int timeoutSeconds);

        #endregion

        /// <summary>
        /// Gets a list of configured projects
        /// </summary>
        /// <returns></returns>
        public List<Project> GetProjects()
        {
            return ProjectsDataAccess.GetProjects();
        }

        /// <summary>
        /// Save the projects to the rtr project configuration file.
        /// </summary>
        /// <param name="projects"></param>
        public void SaveProjects(List<Project> projects)
        {
            ProjectsDataAccess.SaveProjects(projects);
        }

        /// <summary>
        /// Gets a list of test projects.
        /// </summary>
        /// <returns>A <see cref="DataTable"/> containing a list of all test projects.</returns>
        public DataTable GetTestProjects()
        {
            return DataAccess.GetProjects();
        }

        /// <summary>
        /// Deletes the specified test.
        /// </summary>
        /// <param name="testId">The ID of the test to be deleted.</param>
        public void DeleteTest(int testId)
        {
            DataAccess.DeleteTest(testId);
        }

        /// <summary>
        /// Gets a list of tests.
        /// </summary>
        /// <returns>A <see cref="DataTable"/> containing a list of all tests.</returns>
        public DataTable GetTests()
        {
            return DataAccess.GetTests();
        }

        /// <summary>
        /// Gets the specified test row.
        /// </summary>
        /// <param name="testId">The ID of the test to return.</param>
        /// <returns>A <see cref="DataRow"/> containing the specified test information.</returns>
        public TestInfo GetTestRow(int testId)
        {
            var row = DataAccess.GetTestRow(testId);

            if (row != null)
            {
                var result = new TestInfo
                                 {
                                     Id = (int)row["ID"],
                                     ProjectId = (int)row["ProjectID"],
                                     Name = row["Name"].ToString(),
                                     AssemblyPath = row["AssemblyPath"].ToString(),
                                     ConfigPath = row["ConfigPath"].ToString(),
                                     CreatedBy = row["CreatedBy"].ToString(),
                                     CreatedDate = DateTime.Parse(row["CreatedDate"].ToString()),
                                     ModifiedBy = row["ModifiedBy"].ToString(),
                                     ModifiedDate = DateTime.Parse(row["ModifiedDate"].ToString()),
                                     RunConfigPath = row["RunConfigPath"].ToString()
                                 };

                return result;
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Gets a collection of data source information.
        /// </summary>
        /// <param name="appConfigPath">The application configuration file to be parsed.</param>
        /// <returns>A list of <see cref="Contracts.DataSourceInfo"/> containing information for each
        /// data source found in the configuration file.</returns>
        public List<DataSourceInfo> GetDataSources(string appConfigPath)
        {
            var dataSources = new List<DataSourceInfo>();
            DataSourceInfo dsInfo;
            XmlDocument doc;
            XmlNode dsNameNode;
            XmlNode dsBaseUriNode;

            if (File.Exists(appConfigPath))
            {
                doc = new XmlDocument();
                doc.Load(appConfigPath);

                var dataSourceNodes = doc.SelectNodes("/configuration/microsoft.visualstudio.testtools/dataSources/add");

                foreach (XmlNode dsNode in dataSourceNodes)
                {
                    dsNameNode = doc.SelectSingleNode(
                        string.Format("/configuration/connectionStrings/add[@name='{0}']",
                                      dsNode.Attributes.GetNamedItem("connectionString").Value));

                    dsInfo = new DataSourceInfo
                                 {
                                     Name = Utilities.GetAttributeValue(dsNode.Attributes, "name"),
                                     DataSourcePath =
                                         Utilities.GetConnectionStringSetting(
                                         Utilities.GetAttributeValue(dsNameNode.Attributes, "connectionString"),
                                         ConnectionStringSetting.DataSource),
                                     ConnectionStringName =
                                         Utilities.GetAttributeValue(dsNode.Attributes, "connectionString"),
                                     DataTableName = Utilities.GetAttributeValue(dsNode.Attributes, "dataTableName"),
                                     DataAccessMethod =
                                         Utilities.GetAttributeValue(dsNode.Attributes, "dataAccessMethod")
                                 };

                    dsBaseUriNode = doc.SelectSingleNode(
                        string.Format(
                            "/configuration/appSettings/add[@key='{0}']",
                            "DDTCB:" + dsInfo.ConnectionStringName));

                    if (dsBaseUriNode != null)
                    {
                        dsInfo.DataSourceBaseUri = Utilities.GetAttributeValue(dsBaseUriNode.Attributes, "value");
                    }

                    dataSources.Add(dsInfo);
                }
            }

            return dataSources;
        }

        /// <summary>
        /// Gets the test data.
        /// </summary>
        /// <returns>
        /// A dataset containing the test configuration.
        /// </returns>
        public DataSet GetTestData()
        {
            return DataAccess.GetTestData();
        }

        /// <summary>
        /// Saves the test data.
        /// </summary>
        /// <param name="dataSet">The data set.</param>
        public void SaveTestData(DataSet dataSet)
        {
            DataAccess.SaveTestData(dataSet);
        }

        /// <summary>
        /// Runs the test for the test ID passed in.
        /// </summary>
        /// <param name="testId">The ID of the test to be ran.</param>
        /// <param name="testsToRun">A list of the test to run.  The expected format is 'Class.Method'.</param>
        /// <param name="timeoutSeconds"></param>
        /// <returns>
        /// The path of the generated test results file.
        /// </returns>
        public TestResultSummary RunTest(Project project, Test test, List<string> testsToRun, int timeoutSeconds)
        {

            Debug.Assert(File.Exists(RtrSettings.MSTestPath), "MSTest.exe cannot be found at " + RtrSettings.MSTestPath);

            List<DataSourceInfo> dataSources;
            TestResult testRunResults = null;
            TestResultSummary testRunResultsSummary = null;

            // Step 1: Retrieve test details
            //var test = GetTestRow(testId);

            // Step 2: Create a sub-folder within the working folder (for this test run)
            //var project = DataAccess.GetProject(test.ProjectId);
            //var projectName = project["Name"].ToString();
            var testFolder = Path.Combine(
                RtrSettings.WorkingFolder,
                string.Format("{0}.{1}.{2}", project.Name, test.Name, DateTime.Now.ToString("MM-dd-yyyy_HH-mm-ss.fff")));
            Directory.CreateDirectory(testFolder);

            var testContainerFileName = Path.Combine(testFolder, Path.GetFileName(test.AssemblyPath));
            var testRunConfigFileName = test.RunConfigPath;

            // Step 3: Copy the binaries (based on the assembly path) to the working folder
            CopyFiles(
                new DirectoryInfo(Path.GetDirectoryName(test.AssemblyPath)),
                new DirectoryInfo(testFolder));

            // Step 4: Copy the test version of the config file, if one is specified
            var testConfigFileName = testContainerFileName + ".config";
            if (!string.IsNullOrEmpty(test.ConfigPath))
            {
                if (File.Exists(test.ConfigPath))
                {
                    File.Copy(test.ConfigPath, testConfigFileName, true);
                }
                else
                {
                    throw new FileNotFoundException("The specified configuration file: " + test.ConfigPath +
                                                    " could not be found.");
                }
            }

            // Step 5: Copy the data file(s) for this test
            if (File.Exists(test.ConfigPath))
            {
                dataSources = GetDataSources(test.ConfigPath);

                if ((dataSources != null) && (dataSources.Count > 0))
                {
                    // Download each file
                    foreach (var dataSource in dataSources)
                    {
                        if (!string.IsNullOrEmpty(dataSource.DataSourceBaseUri))
                        {
                            DownloadFile(dataSource.DataSourceBaseUri, dataSource.DataSourcePath);
                        }
                    }
                }
            }

            // Step 6: Build command line for MSTest
            var filesToSave = new List<string>();
            var testResultsFilename = Path.Combine(testFolder, Path.GetFileName(testFolder) + ".trx");
            filesToSave.Add(testResultsFilename);
            var msTestRunLog = Path.Combine(testFolder, Path.GetFileName(testFolder) + ".txt");

            var testsArgs = new StringBuilder();
            foreach (var tests in testsToRun)
            {
                testsArgs.Append(" /test:" + tests + " ");
            }

            if (!string.IsNullOrEmpty(testRunConfigFileName))
            {
                testsArgs.AppendFormat(" /runconfig:\"{0}\" ", testRunConfigFileName);
            }

            var msTestArgs = string.Format(
                "/nologo /testcontainer:\"{0}\" /resultsfile:\"{1}\" {3}",
                testContainerFileName,
                testResultsFilename,
                msTestRunLog,
                testsArgs);

            // Step 7: Run the test(s)
            var startInfo = new ProcessStartInfo(RtrSettings.MSTestPath, msTestArgs);
            startInfo.WindowStyle = ProcessWindowStyle.Hidden;
            //startInfo.RedirectStandardOutput = true;
            //startInfo.UseShellExecute = false;
            var testProcess = Process.Start(startInfo);

            // Wait for the tests to complete before continuing - timeout after 10 minutes
            if (testProcess.WaitForExit(timeoutSeconds * 1000))
            {
                // Step 8: Parse the test results
                try
                {
                    testRunResultsSummary = ParseTestResultsSummary(testResultsFilename);
                    testRunResults = ParseTestResults(testResultsFilename);
                }
                catch (FileNotFoundException ex)
                {
                    throw new FileNotFoundException(
                        "The test results file: " + testResultsFilename + " was not found.", ex);
                }
            }
            else
            {
                try
                {
                    testProcess.Kill();
                }
                catch (Exception)
                {
                    // ignore any exception that occurred while trying to kill the process.
                }
                throw new TimeoutException("The tests have timed out during execution.  The tests were not completed.");
            }

            // Step 8: Remove temporary files
            testRunResults.MultiAssertResults.ForEach(
                result => { filesToSave.Add(result.FileName); });

            // Copy the save files to a safe directory
            var safeDirectory = RtrSettings.WorkingFolder;
            for (var i = 0; i < filesToSave.Count; i++)
            {
                var destinationFile = Path.Combine(safeDirectory, Path.GetFileName(filesToSave[i]));
                File.Copy(filesToSave[i], destinationFile);
                filesToSave[i] = destinationFile;
            }

            // Delete the working folder and all contents, and then recreate it
            Directory.Delete(testFolder, true);
            Directory.CreateDirectory(testFolder);

            // Move the save files back to the working folder
            for (var i = 0; i < filesToSave.Count; i++)
            {
                var destinationFile = Path.Combine(testFolder, Path.GetFileName(filesToSave[i]));
                File.Move(filesToSave[i], destinationFile);
                filesToSave[i] = destinationFile;
            }

            return testRunResultsSummary;
        }

        /// <summary>
        /// Gets the test result history.
        /// </summary>
        /// <param name="test">The ID of the test.</param>
        /// <returns></returns>
        public List<TestResultSummary> GetTestResultHistory(Project project, Test test)
        {
            var searchPattern = project.Name + "." + test.Name + ".*.*";
            var testDirectories = Directory.GetDirectories(RtrSettings.WorkingFolder, searchPattern);

            var results = new List<TestResultSummary>();

            foreach (var directory in testDirectories)
            {
                // Find the trx file in the results directory
                var testResultFiles = Directory.GetFiles(directory, "*.trx");

                // There should only be one trx file in the directory, so we choose the first item incase this isn't true
                if (testResultFiles.Length > 0)
                {
                    try
                    {
                        results.Add(ParseTestResultsSummary(testResultFiles[0]));
                    }
                    catch (Exception)
                    {
                        // we are just going to ignore any parse error that occurs.
                        // TODO handle parse errors more intelligently
                    }
                }
            }

            return results;
        }

        public TestResult GetTestResult(string path)
        {
            return ParseTestResults(path);
        }

        public int AddTest(int projectId, string name)
        {
            return 0;
        }

        public void DeleteTestResultHistoryItems(List<TestResultSummary> historyItemsToDelete)
        {
            foreach (var testResult in historyItemsToDelete)
            {
                var testPath = Path.GetDirectoryName(testResult.Path);
                Directory.Delete(testPath, true);
            }
        }

        /// <summary>
        /// Parses the test results summary.
        /// </summary>
        /// <param name="testResultsPath">The test results path.</param>
        /// <returns>Test result summary.</returns>
        public TestResultSummary ParseTestResultsSummary(string testResultsPath)
        {
            var summary = new TestResultSummary();

            XmlNamespaceManager nsm;
            var doc = GetTestResultXmlDocument(testResultsPath, out nsm);

            // Check the XML file to ensure it is an actual test results file
            if (doc.SelectSingleNode("ns:TestRun", nsm) == null)
            {
                return null;
            }

            // Store the path
            summary.Path = testResultsPath;

            summary.TestRunName = doc.SelectSingleNode("ns:TestRun", nsm).Attributes["name"].Value;
            summary.TestStartTime =
                DateTime.Parse(doc.SelectSingleNode("ns:TestRun/ns:Times", nsm).Attributes["start"].Value);
            summary.TestRunId = doc.SelectSingleNode("ns:TestRun", nsm).Attributes["id"].Value;

            return summary;
        }

        /// <summary>
        /// Parses the test results.
        /// </summary>
        /// <param name="testResultsPath">The test results path.</param>
        /// <returns></returns>
        public TestResult ParseTestResults(string testResultsPath)
        {
            var testResult = new TestResult();
            TestResultInfo testResultInfo;
            TestResultInfo dataDrivenResultInfo;
            XmlNamespaceManager nsm;
            var doc = GetTestResultXmlDocument(testResultsPath, out nsm);
            XmlNode unitTestNode;

            // Check the XML file to ensure it is an actual test results file
            if (doc.SelectSingleNode("ns:TestRun", nsm) == null)
            {
                return null;
            }

            // Store the path
            testResult.Path = testResultsPath;

            // Look for related XML files
            var multiAssertFiles =
                new List<string>(
                    Directory.GetFiles(Path.GetDirectoryName(testResult.Path), "*" + MultiAssertFileSuffix));
            multiAssertFiles.ForEach(file =>
                                         {
                                             var multiAssertResult = new TestResultMultiAssertResult();
                                             multiAssertResult.FileName = file;
                                             multiAssertResult.FileContents = File.ReadAllText(file);
                                             testResult.MultiAssertResults.Add(multiAssertResult);
                                         });

            // Gather overall test statistics
            testResult.TestRunName = doc.SelectSingleNode("ns:TestRun", nsm).Attributes["name"].Value;
            testResult.TestStartTime =
                DateTime.Parse(doc.SelectSingleNode("ns:TestRun/ns:Times", nsm).Attributes["start"].Value);
            testResult.TestRunId = doc.SelectSingleNode("ns:TestRun", nsm).Attributes["id"].Value;
            testResult.Outcome = doc.SelectSingleNode("ns:TestRun/ns:ResultSummary", nsm).Attributes["outcome"].Value;

            testResult.TotalCount =
                int.Parse(
                    Utilities.GetAttributeValue(doc.SelectSingleNode("ns:TestRun/ns:ResultSummary/ns:Counters", nsm).Attributes, "total", "0"));
            testResult.TotalExecuted =
                int.Parse(
                    Utilities.GetAttributeValue(doc.SelectSingleNode("ns:TestRun/ns:ResultSummary/ns:Counters", nsm).Attributes, "executed", "0"));
            testResult.TotalPassed =
                int.Parse(
                    Utilities.GetAttributeValue(doc.SelectSingleNode("ns:TestRun/ns:ResultSummary/ns:Counters", nsm).Attributes, "passed", "0"));
            testResult.TotalFailed =
                int.Parse(
                    Utilities.GetAttributeValue(doc.SelectSingleNode("ns:TestRun/ns:ResultSummary/ns:Counters", nsm).Attributes, "failed", "0"));
            testResult.TotalTimedOut =
                int.Parse(
                    Utilities.GetAttributeValue(doc.SelectSingleNode("ns:TestRun/ns:ResultSummary/ns:Counters", nsm).Attributes, "timeout", "0"));
            testResult.TotalInconclusive =
                int.Parse(
                    Utilities.GetAttributeValue(doc.SelectSingleNode("ns:TestRun/ns:ResultSummary/ns:Counters", nsm).Attributes, "inconclusive", "0"));

            // Create a list of nodes that we'll be working with
            var unitTestResults = doc.SelectNodes("ns:TestRun/ns:Results/ns:UnitTestResult", nsm);

            // Iterate through each of the test results and gather the required information
            foreach (XmlNode unitTestResultNode in unitTestResults)
            {
                testResultInfo = new TestResultInfo();

                // Gather test-level properties
                testResultInfo.TestId = Utilities.GetAttributeValue(unitTestResultNode.Attributes, "testId");
                testResultInfo.ExecutionId = Utilities.GetAttributeValue(unitTestResultNode.Attributes, "executionId");
                testResultInfo.IsDataDriven =
                    (Utilities.GetAttributeValue(unitTestResultNode.Attributes, "resultType") == "DataDrivenTest");
                testResultInfo.Outcome = Utilities.GetAttributeValue(unitTestResultNode.Attributes, "outcome");
                testResultInfo.ResultType = Utilities.GetAttributeValue(unitTestResultNode.Attributes, "resultType");

                // default the message to the outcome.  If the test failed then the message will be updated with the failure message.
                testResultInfo.Message = testResultInfo.Outcome;

                // Gather method-level properties
                unitTestNode =
                    doc.SelectSingleNode(
                        string.Format("ns:TestRun/ns:TestDefinitions/ns:UnitTest[@id='{0}']/ns:TestMethod",
                                      testResultInfo.TestId), nsm);

                testResultInfo.MethodName = Utilities.GetAttributeValue(unitTestNode.Attributes, "name");
                testResultInfo.ClassName = Utilities.GetAttributeValue(unitTestNode.Attributes, "className");

                if (testResultInfo.IsDataDriven)
                {
                    // The data-driven test results follow a slightly different format
                    var ddResults = unitTestResultNode.SelectNodes("ns:InnerResults/ns:UnitTestResult", nsm);

                    if (ddResults != null)
                    {
                        if (testResultInfo.Outcome == "Passed")
                        {
                            testResultInfo.Message = "[Data-Driven Test - all tests passed.]";
                        }
                        else
                        {
                            testResultInfo.Message = "[Data-Driven Test - one or more tests failed.]";
                        }

                        foreach (XmlNode ddNode in ddResults)
                        {
                            // Process data-driven test results
                            dataDrivenResultInfo = new TestResultInfo();

                            // Gather test-level properties
                            dataDrivenResultInfo.TestId = Utilities.GetAttributeValue(ddNode.Attributes, "testId");
                            dataDrivenResultInfo.ExecutionId = Utilities.GetAttributeValue(ddNode.Attributes,
                                                                                           "executionId");
                            dataDrivenResultInfo.Outcome = Utilities.GetAttributeValue(ddNode.Attributes, "outcome");
                            dataDrivenResultInfo.ResultType = Utilities.GetAttributeValue(ddNode.Attributes, "resultType");
                            dataDrivenResultInfo.DataRowInfo = int.Parse(Utilities.GetAttributeValue(ddNode.Attributes, "dataRowInfo"));
                            dataDrivenResultInfo.IsDataDriven =
                                (Utilities.GetAttributeValue(unitTestResultNode.Attributes, "resultType") ==
                                 "DataDrivenTest");

                            // Gather method-level properties
                            dataDrivenResultInfo.ClassName = testResultInfo.ClassName;
                            dataDrivenResultInfo.MethodName = testResultInfo.MethodName;

                            // If the test failed, gather failure details
                            var outcome = Utilities.GetAttributeValue(ddNode.Attributes, "outcome");

                            if (outcome == "Failed")
                            {
                                dataDrivenResultInfo.Message =
                                    ddNode.SelectSingleNode("ns:Output/ns:ErrorInfo/ns:Message", nsm).InnerText;
                                dataDrivenResultInfo.StackTrace =
                                    ddNode.SelectSingleNode("ns:Output/ns:ErrorInfo/ns:StackTrace", nsm).InnerText;
                            }

                            testResultInfo.DataDrivenResults.Add(dataDrivenResultInfo);
                        }
                    }
                }
                else
                {
                    // Retrieve results for failed non-data-driven test
                    if (testResultInfo.Outcome == "Failed")
                    {
                        testResultInfo.Message =
                            unitTestResultNode.SelectSingleNode("ns:Output/ns:ErrorInfo/ns:Message", nsm).InnerText;
                        var stackTraceNode =
                            unitTestResultNode.SelectSingleNode("ns:Output/ns:ErrorInfo/ns:StackTrace", nsm);
                        if (stackTraceNode != null)
                        {
                            testResultInfo.StackTrace = stackTraceNode.InnerText;
                        }
                        else
                        {
                            testResultInfo.StackTrace = string.Empty;
                        }
                    }
                }

                testResult.TestResults.Add(testResultInfo);
            }

            testResult.MultiAssertReport = ApplyXslTransformation(testResult);

            return testResult;
        }

        /// <summary>
        /// Gets a two level heirarchy of Classes decorated with the TestClassAttribute and Methods decorated with the TestMethodAttribute.
        /// </summary>
        /// <param name="testId">The ID of the test to process.</param>
        /// <returns>A 2 level hierchial class/method list.</returns>
        public List<TestClass> GetTestMethods(Test test)
        {
            List<TestClass> results;
            if (!File.Exists(test.AssemblyPath))
            {
                var errorClass = new TestClass();
                errorClass.Name = "Assembly not found";
                results = new List<TestClass>();
                results.Add(errorClass);
                return results;
            }

            var thisAssembly = Assembly.GetExecutingAssembly();

            // Configure AppDomainSetup
            var domainSetup = new AppDomainSetup();
            var uriPath = new Uri(thisAssembly.CodeBase);
            domainSetup.ApplicationBase = Path.GetDirectoryName(uriPath.AbsolutePath);
            domainSetup.PrivateBinPath = Path.GetDirectoryName(uriPath.AbsolutePath);
            //domainSetup.DisallowBindingRedirects = false;
            //domainSetup.DisallowCodeDownload = true;
            domainSetup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;

            // Configure the AppDomain
            object[] evidence = { new Zone(SecurityZone.MyComputer) };
            var trustedEvidence = new Evidence(evidence, null);
            var testMethodsDomain = AppDomain.CreateDomain("GetTestMethods", trustedEvidence, domainSetup);

            // Get the test methods from the AppDomain
            var rtrLib = (RtrLib)testMethodsDomain.CreateInstanceAndUnwrap(thisAssembly.FullName, GetType().FullName);
            results = rtrLib.InternalGetTestMethods(test);

            // Cleanup
            AppDomain.Unload(testMethodsDomain);

            return results;
        }

        /// <summary>
        /// Gets a two level heirarchy of Classes decorated with the TestClassAttribute and Methods decorated with the TestMethodAttribute.
        /// </summary>
        /// <param name="test"></param>
        /// <returns></returns>
        internal List<TestClass> InternalGetTestMethods(Test test)
        {
            //
            // Build a two level heirarchy of Classes and Methods
            // Class 1
            // --- Method 1
            // --- Methid 2
            // Class 2
            // --- Method 1
            // --- Methid 2
            // etc.


            var targetAssembly = Assembly.LoadFrom(test.AssemblyPath);
            var msTestAssembly =
                Assembly.Load(
                    "Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL");

            // TODO convert to use ReflectionOnly this requires less permissions then the method below. 
            // This in turn will not require the calling app to have Code Access Security setup to Full Trust.
            // This requires calls to CustomAttributeData instead of GetCustomAttributes.
            ////System.AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += new ResolveEventHandler(CurrentDomain_ReflectionOnlyAssemblyResolve);
            ////System.Reflection.Assembly targetAssembly = System.Reflection.Assembly.ReflectionOnlyLoad(test.AssemblyPath);
            ////System.Reflection.Assembly targetAssembly = System.Reflection.Assembly.ReflectionOnlyLoadFrom(test.AssemblyPath);


            Func<Type, bool> hasTestClassAttribute = (t) =>
            {
                return t.GetCustomAttributes(msTestAssembly.GetType("Microsoft.VisualStudio.TestTools.UnitTesting.TestClassAttribute"), true).Length > 0;
            };

            Func<MethodInfo, bool> hasTestMethodAttribute = (m) =>
            {
                return m.GetCustomAttributes(
                    msTestAssembly.GetType(
                    "Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute"),
                    true).Length > 0;
            };

            Func<MethodInfo, bool> hasDataSourceAttribute = (m) =>
            {
                return m.GetCustomAttributes(
                    msTestAssembly.GetType(
                    "Microsoft.VisualStudio.TestTools.UnitTesting.DataSourceAttribute"),
                    true).Length > 0;
            };

            // Get all the classes from the assembly with the [TestClass] attribute
            IEnumerable<Type> classes = from type in targetAssembly.GetTypes()
                                        where hasTestClassAttribute(type)
                                        select type;

            List<TestClass> results = new List<TestClass>();

            if (classes.Count() == 0)
            {
                var errorClass = new TestClass
                {
                    Name = "No classes in assembly are marked with TestClassAttribute",
                    IsTestable = false
                };
                results.Add(errorClass);

                var errorMethod = new TestMethod
                {
                    Name = "No methods in class are marked with the TestMethodAttribute",
                    IsTestable = false
                };
                errorClass.TestMethods.Add(errorMethod);

                return results;
            }

            // Loop through identified classes and find the [TestMethod] methods.
            foreach (Type types in classes)
            {
                TestClass testClass = new TestClass
                {
                    Name = types.Name,
                    IsTestable = true
                };

                results.Add(testClass);

                var testMethods =
                    from testMethod in types.GetMethods()
                    where hasTestMethodAttribute(testMethod)
                    select new TestMethod
                    {
                        Name = testMethod.Name,
                        // Data driven tests have an additional attribute.  Note those tests.
                        DataDriven = hasDataSourceAttribute(testMethod),
                        IsTestable = true
                    };

                if (testMethods.Count() == 0)
                {
                    var errorMethod = new TestMethod
                    {
                        Name = "No methods in class are marked with the TestMethodAttribute",
                        IsTestable = false
                    };
                    testClass.TestMethods.Add(errorMethod);
                }
                else
                {
                    foreach (TestMethod method in testMethods)
                    {
                        testClass.TestMethods.Add(method);
                    }
                }
            }

            return results;
        }

        private Assembly CurrentDomain_ReflectionOnlyAssemblyResolve(object sender, ResolveEventArgs args)
        {
            return Assembly.ReflectionOnlyLoad(args.Name);
        }

        private XmlDocument GetTestResultXmlDocument(string testResultsPath, out XmlNamespaceManager nsm)
        {
            var doc = new XmlDocument();
            nsm = new XmlNamespaceManager(doc.NameTable);

            // Create namespace manager for the namespace used by MSTest.exe
            nsm.AddNamespace("ns", "http://microsoft.com/schemas/VisualStudio/TeamTest/2006");

            // Load the results document
            doc.Load(testResultsPath);
            return doc;
        }

        private string ApplyXslTransformation(TestResult testResult)
        {
            // Load up the XSL file.
            var settings = new XsltSettings(true, false);
            var xslt = new XslCompiledTransform();
            var resource = GetResource("RemoteTestRunner.BusinessLogic.MultiAssertReport.MultiAssertReport.xsl");
            var reader = new StringReader(resource);
            var xmlReader = new XmlTextReader(reader);
            xslt.Load(xmlReader, settings, null);

            // Prepare the output stream.
            var stringWriter = new StringWriter();
            var writer = new XmlTextWriter(stringWriter);

            // Transform the raw data and return the result.
            // Create an XML stream that looks like <Files><File>c:\...</File></Files>
            var externalFiles = new XElement(
                "Files",
                from f in testResult.MultiAssertResults
                select new XElement("File", f.FileName));

            var doc = new XmlDocument();
            doc.LoadXml(externalFiles.ToString());
            XmlNode node = doc;

            xslt.Transform(node, writer);
            return stringWriter.ToString();
        }

        #endregion

        #region Private Methods

        private static string GetResource(string resourceName)
        {
            var assembly = Assembly.GetExecutingAssembly();
            TextReader textReader = new StreamReader(assembly.GetManifestResourceStream(resourceName));
            var result = textReader.ReadToEnd();
            textReader.Close();

            return result;
        }

        /// <summary>
        /// Recursively copies all files and folders from the source folder to the target
        /// folder.
        /// </summary>
        /// <param name="source">The source folder.</param>
        /// <param name="target">The target folder.</param>
        private void CopyFiles(DirectoryInfo source, DirectoryInfo target)
        {
            // Check if the target directory exists, if not, create it.
            if (Directory.Exists(target.FullName) == false)
            {
                Directory.CreateDirectory(target.FullName);
            }

            // Copy each file into it’s new directory.
            foreach (var fi in source.GetFiles())
            {
                fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true);
            }

            // Copy each subdirectory using recursion.
            foreach (var diSourceSubDir in source.GetDirectories())
            {
                var nextTargetSubDir =
                    target.CreateSubdirectory(diSourceSubDir.Name);
                CopyFiles(diSourceSubDir, nextTargetSubDir);
            }
        }

        /// <summary>
        /// Downloads the specified file.
        /// </summary>
        /// <param name="requestUriString">The URI of the file to be downloaded.</param>
        /// <param name="desinationPath">The desination path.</param>
        /// <returns><c>true</c> if the download is successful; otherwise, <c>false</c>.</returns>
        private bool DownloadFile(string requestUriString, string desinationPath)
        {
            var success = true;

            try
            {
                var client = new WebClient { UseDefaultCredentials = true };

                client.DownloadFile(requestUriString, HttpUtility.UrlDecode(desinationPath));
            }
            catch
            {
                success = false;
            }

            return success;
        }

        #endregion

    }
}